﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection.Emit;

namespace DLR05
{
	/// <summary>
	/// This application expands on DLR04 by giving the SimpleObject (previously known as Scope) object
	/// the ability to dynamically get and set its members.  SimpleObject is now directly invocable.
	/// </summary>
	public class Program
	{
		public static void Main(string[] args)
		{
			//SimpleObjectTest();
			//SimpleObjectRetest();
			DynamicMethodTest();
			//WriteSomeStuff();
		}

		public static void SimpleObjectTest()
		{
			dynamic scope = CreateTestObject();

			scope.n1 = 5;
			scope.n2 = 7;
			scope();

			Console.WriteLine("Member dump:");
			foreach (var item in scope)
			{
				Console.WriteLine("{0} = {1}", item.Name, item.Data);
			}
			Console.WriteLine();

			Console.WriteLine("Results of dynamic add call: {0}", scope.add(13, 24));

			new DynamicMethod(scope.Expression).Save("SimpleObjectTest.xml");
		}

		public static void SimpleObjectRetest()
		{
			dynamic scope = new DynamicMethod("SimpleObjectTest.xml");

			scope.n1 = 5;
			scope.n2 = 7;
			scope();

			Console.WriteLine("Member dump:");

			foreach (var item in scope)
			{
				Console.WriteLine("{0} = {1}", item.Name, item.Data);
			}
			Console.WriteLine();

			Console.WriteLine("Results of dynamic add call: {0}", scope.add(13, 24));
		}

		public static void DynamicMethodTest()
		{
			Console.WriteLine("--- TEST 1 ---");

			dynamic fact = new DynamicMethod("Content/Factorial.xml");
			for (int n = 0; n < 10; n++)
			{
				Console.WriteLine(fact(n));
			}

			Console.WriteLine("--- TEST 2 ---");

			dynamic writer = new DynamicMethod("Content/WriteSomeStuff.xml");
			writer(10);
			writer(20);
			writer(30);

			Console.WriteLine("--- TEST 3 ---");

			dynamic scope = new DynamicMethod("Content/SimpleObjectTest.xml");
			scope.n1 = 5;
			scope.n2 = 7;
			scope();
			Console.WriteLine("Member dump:");
			foreach (var item in scope)
			{
				Console.WriteLine("{0} = {1}", item.Name, item.Data);
			}
			Console.WriteLine();
			Console.WriteLine("Results of dynamic add call: {0}", scope.add(13, 24));
		}

		/*
		 *  <Lambda>
		 *      <Lambda.Parameters>
		 *          <Parameter Type="int" Name="arg" />
		 *      </Lambda.Parameters>
		 *      <Block>
		 *          <Call Method="System.Console.WriteLine" Signature="System.String">
		 *              <Call.Arguments>
		 *                  <Constant Value="Print arg:" />
		 *              </Call.Arguments>
		 *          </Call>
		 *          <Call Method="System.Console.WriteLine" Signature="System.Int32" Argument="arg" />
		 *      </Block>
		 *  </Lambda>
		 */

		public static void WriteSomeStuff()
		{
			ParameterExpression param = Expression.Parameter(typeof(int), "arg");

			MethodCallExpression firstMethodCall = Expression.Call(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("Print arg:"));
			MethodCallExpression secondMethodCall = Expression.Call(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), param);

			BlockExpression block = Expression.Block(firstMethodCall, secondMethodCall);

			var expr = Expression.Lambda<Action<int>>(block, new ParameterExpression[] { param });
			expr.Compile()(10);

			new DynamicMethod(expr).Save("WriteSomeStuff.xml");

			dynamic method = new DynamicMethod("WriteSomeStuff.xml");
			method(20);
		}

		private static DynamicMethod CreateTestObject()
		{
			var self = Expression.Parameter(typeof(DynamicMethod), "self");

			var n1 = Expression.Parameter(typeof(object), "n1");
			var n2 = Expression.Parameter(typeof(object), "n2");

			var addBody = new List<Expression>()
			{
				Expression.Assign(
					ExpressionEx.SymbolExpression(self, "add"),
					Expression.Lambda(Expression.Block(
						Expression.Assign(ExpressionEx.SymbolExpression(self, "n3"), ExpressionEx.DynamicAdd(n1, n2)),
						ExpressionEx.DynamicAdd(n1, n2)
					), n1, n2)
				),
				Expression.Assign(
					ExpressionEx.SymbolExpression(self, "result"),
					ExpressionEx.DynamicInvoke(
						ExpressionEx.SymbolExpression(self, "add"),
						ExpressionEx.SymbolExpression(self, "n1"),
						ExpressionEx.SymbolExpression(self, "n2")
					)
				)
			};

			return new DynamicMethod(Expression.Lambda<Func<DynamicMethod, object>>(Expression.Block(addBody), self));
		}
	}
}